#ifndef __PROCESSPOOL_HPP__
#define __PROCESSPOOL_HPP__

#include <iostream>
#include "task.hpp"
#include <vector>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

// 描述
class Channel
{
public:
    Channel(int wrd, int id)
        : _wfd(wrd), _id(id)
    {
        _ref = "channel-" + std::to_string(_wfd) + "-"+ std::to_string(_id);
    }
    
    ~Channel() {}

    int Wfd() { return _wfd; }
    int Id() { return _id; }
    std::string &Ref() { return _ref; }

    void closewfd()
    {
        int n = close(_wfd);
        (void)n;
    }

    void waitid()
    {
        int n = waitpid(_id, nullptr, 0);
        if(n < 0) std::cerr<<"wiat error" << std::endl;
    }


    void NotifyTask(int taskcode)
    {
        int n = write(_wfd, &taskcode, sizeof(taskcode));
        (void)n;
    }

private:
    int _wfd;         // 信道号
    int _id;          // 对应子进程
    std::string _ref; // 信道描述信息
};

// 组织
class ChannelManager
{
public:
    ChannelManager()
    :_next(0)
    {}
    ~ChannelManager() {}

    void manage(int wrd, int id)
    {
        _channels.emplace_back(wrd, id);
    }

    Channel& Select()
    {
        auto& c = _channels[_next];
        _next++;
        _next %= _channels.size();
        return c;
    }

    void DestroyChannel()
    {
        for(auto& channel : _channels)
        {
            channel.closewfd();
            std::cout << channel.Ref() << "closed !" << std::endl;
        }
    }

    void RecycleProcess()
    {
        for(auto& channel : _channels)
        {
            channel.waitid();
            std::cout << channel.Ref() << "recycled !" << std::endl;
        }
    }

    void DestoryAndRecycle()
    {
        // 如果采用关闭一个信道就直接等待子进程是无法销毁进程池的
        // 在fork子进程时，我们只是关闭了该子进程自己的写端，但是父进程的读端一直在增加
        // 后面fork的子进程会拷贝父进程的写端，导致后面的子进程除了自己的读端外，还有前面所有兄弟进程的的写端
        // 假如有五个子进程，对于第一个子进程来说，它的写端就有5个进程指向，父进程以及后面的四个兄弟进程
        // 此时我们从头开始关闭信道，并等待子进程，关闭第一个后，我们进行等待，但是其写端并没有完全关闭完，导致该子进程依旧会在read处阻塞。
        for(auto& channel : _channels)
        {
            channel.closewfd();
            std::cout << channel.Ref() << "closed!" << std::endl;
            channel.waitid();
            std::cout << channel.Ref() << "recycled !" << std::endl;
        }

        // 解决方案1：倒着关闭信道并回收子进程
        // 对于最后一个子进程来说，它对应的信道只有父进程指向，所以关闭该信道，read的返回值就会为0，此时就可以退出read阻塞状态，进行等待
        // 最后一个子进程都退出了，它的文件描述符表也就释放了，指向前面兄弟的写端也就释放了
        //for(int i = _channels.size() - 1; i >= 0; i--)
        //{
        //    _channels[i].closewfd();
        //    std::cout << _channels[i].Ref() << "closed!" << std::endl;
        //    _channels[i].waitid();
        //    std::cout << _channels[i].Ref() << "recycled !" << std::endl;
        //}

        // 解决方案2：子进程创建完毕后，关闭指向对应的兄弟进程的写端
        // 具体实现见进程的初始化
    }

    void CloseChildWrite()
    {
        for(auto channel : _channels)
        {
            close(channel.Wfd());
        }
    }
    

#define DEBUG
#ifdef DEBUG
    void debug()
    {
        for (auto &channel : _channels)
        {
            std::cout << channel.Wfd() << std::endl;
            std::cout << channel.Id() << std::endl;
            std::cout << channel.Ref() << std::endl;
        }
    }

#endif

private:
    std::vector<Channel> _channels;
    int _next; // 轮询使用信道
};

const int defaultPoolCount = 5;

// 进程池
class ProcessPool
{
public:
    ProcessPool(int n = defaultPoolCount) 
    :_process_count(n)
    {
        // 注册任务
        _tm.Register(0, PrintLog);
        _tm.Register(1, DownLoad);
        _tm.Register(2, UPLoad);
    }
    ~ProcessPool() {}

    // 执行任务
    void Work(int rfd)
    {
        int code = 0;
        while (true)
        {
            ssize_t n = read(rfd, &code, sizeof(code));
            // std::cout << "n:" << n << std::endl;

            if (n > 0)
            {
                if(n != sizeof(code))
                    continue;

                std::cout << "开始执行任务" << std::endl;
                _tm.Exectue(code);
            }
            else if (n == 0)
            {
                //std::cout << "读到文件末尾" << std::endl;
                break;
            }
            else
            {
                std::cerr << "读取错误" << std::endl;
                break;
            }
        }
    }

    bool InitPool()
    {
        for (int i = 0; i < _process_count; ++i)
        {
            // 1.打开管道文件
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
            {
                std::cerr << "pipe error" << std::endl;
                return -1;
            }

            // 2.创建子进程，并构建单向通信信道 -> fathrer write, child read
            pid_t id = fork();
            if (id < 0)
            {
                std::cerr << "fork errror" << std::endl;
            }
            else if (id == 0)
            {
                // child
                // 法一：子进程的文件描述符表拷贝自父进程
                // 父进程由于一直关闭读端，保留写端，所以3号一直存在，写端一直递增
                // 所以对于进程来说它的读端都是三号，而自己的写端也就是递增之后的结果
                // 这两个文件描述符之间的都是其他兄弟进程的写端，我们只需要关闭即可
                //for(int i = pipefd[0] + 1; i <= pipefd[1]; ++i)
                //{
                //    close(i);
                //}

                // 法二：父进程有自己的channelmanager，子进程是由父进程创建的，他们的代码和数据是共享的，所以子进程也可以看见自己的channelmanager
                // 而子进程获取的channelmanager是父进程还没有将本次子进程插入进vector的数据
                // 所以，此时我们就可以直接将子进程vector中的写端关闭即可
                _cm.CloseChildWrite();

                // 关闭的只是自己之前创建的子进程的写端，还需要关闭自己的写端
                close(pipefd[1]); // 关闭自己的写端
                Work(pipefd[0]); // 执行任务
                close(pipefd[0]);
                exit(0);
            }
            else
            {
                // father
                close(pipefd[0]);

                // 3.将创建出来的信道与子进程关联
                _cm.manage(pipefd[1], id);
            }
        }
#ifdef DEBUG
        _cm.debug();
        sleep(10);
#endif
        return true;
    }

    // 选择任务，选择信道
    void RunTask()
    {
        // 1.选择任务
        int taskcode = _tm.taskcode();
        std::cout << "任务已选择..." << std::endl;

        // 2.选择信道
        auto& channel = _cm.Select();
        std::cout << "信道已选择--->" << channel.Ref() << std::endl;

        // 3.发布任务
        channel.NotifyTask(taskcode);
        std::cout << "任务已通知..." << std::endl;

    }

    // 销毁进程池
    void Destroy()
    {
        // // 1.关闭所有信道
        // _cm.DestroyChannel();

        // // 2.回收子进程
        // _cm.RecycleProcess();

        // 可不可以关闭一个信道就立刻回收呢？
        _cm.DestoryAndRecycle();
        // 那么为什么我们上面先将所有的信道关闭，再回收子进程就可以了呢？
        // 按照上面的分析，关闭第一个信道，它的写端并不会全部关闭，子进程不会退出
        // 但是我们并没有进行等待，而是直接关闭下一个信道，所以等到所有的信道关闭完成，此时就会有类似递归回退的效果
        // 从后往前，子进程的read读到返回值0，开始退出
        // 自此，我们才进行等待
    }

private:
    ChannelManager _cm;
    TaskManager _tm;
    int _process_count;
};

#endif